package ace.cmp.data.methodname.sql.mybatisplus.core.factory;

import ace.cmp.data.methodname.method.name.sql.core.enums.SqlCommandTypeEnum;
import ace.cmp.data.methodname.method.name.sql.core.resolver.MethodNameToSubjectResolver;
import ace.cmp.data.methodname.method.name.sql.core.resolver.impl.MethodNameToSubjectResolverImpl;
import ace.cmp.data.methodname.sql.mybatisplus.core.annotations.InjectMethodNameToSql;
import ace.cmp.data.methodname.sql.mybatisplus.core.enums.InjectLevelEnum;
import ace.cmp.data.methodname.sql.mybatisplus.core.method.*;
import ace.cmp.data.methodname.sql.mybatisplus.core.properties.MybatisPlugMethodNameToSqlProperties;
import com.baomidou.mybatisplus.core.injector.AbstractMethod;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;

/**
 * @author caspar
 * @date 2023/2/23 15:37
 */
@Slf4j
public class MethodNameToSqlMethodFactoryImpl implements MethodNameToSqlMethodFactory {

  private final MybatisPlugMethodNameToSqlProperties properties;

  public MethodNameToSqlMethodFactoryImpl(MybatisPlugMethodNameToSqlProperties properties) {
    this.properties = properties;
  }

  @Override
  public List<AbstractMethod> getMethods(Class<?> mapperClass, TableInfo tableInfo) {
    if (this.properties.getEnable()) {
      List<AbstractMethod> methods =
          Arrays.stream(mapperClass.getMethods())
              .map(
                  method -> {
                    if (this.injectMethodIsEnable(mapperClass, method) == false) {
                      log.debug(
                          "mapper:{} method:{},已经过滤不进行解析", mapperClass.getName(), method.getName());
                      return null;
                    }
                    if (this.isMybatisPlusBaseMapper(mapperClass, method)) {
                      log.debug(
                          "mapper:{} method:{},已经过滤不进行解析", mapperClass.getName(), method.getName());
                      return null;
                    }
                    MethodNameToSubjectResolver subjectResolver =
                        new MethodNameToSubjectResolverImpl(method.getName());
                    AbstractMethod sqlMethod =
                        this.create(subjectResolver, mapperClass, method, tableInfo);
                    if (Objects.isNull(sqlMethod)) {
                      log.debug(
                          "mapper:{} method:{},解析失败", mapperClass.getName(), method.getName());
                      return null;
                    }
                    return sqlMethod;
                  })
              .filter(p -> Objects.nonNull(p))
              .collect(Collectors.toList());

      return methods;
    }
    return new ArrayList<>();
  }

  private boolean isMybatisPlusBaseMapper(Class<?> mapperClass, Method method) {
    Optional<Class<?>> baseMapperClass =
        Arrays.stream(mapperClass.getInterfaces())
            .filter(p -> p.isAssignableFrom(BaseMapper.class))
            .findFirst();
    if (baseMapperClass.isEmpty()) {
      return false;
    }
    return Arrays.stream(BaseMapper.class.getMethods())
        .anyMatch(p -> p.getName().equals(method.getName()));
  }

  private boolean injectMethodIsEnable(Class<?> mapperClass, Method method) {

    InjectMethodNameToSql classAnnotation = mapperClass.getAnnotation(InjectMethodNameToSql.class);
    InjectMethodNameToSql methodAnnotation = method.getAnnotation(InjectMethodNameToSql.class);

    if (Objects.nonNull(methodAnnotation)) {
      return methodAnnotation.enable();
    }
    if (Objects.nonNull(classAnnotation)) {
      return classAnnotation.enable();
    }

    return this.properties.getInjectLevel().equals(InjectLevelEnum.ALL);
  }

  private AbstractMethod create(
      MethodNameToSubjectResolver subjectResolver,
      Class mapperClass,
      Method method,
      TableInfo tableInfo) {
    try {
      SqlCommandTypeEnum sqlCommandTypeEnum = subjectResolver.getSqlCommandType();

      if (SqlCommandTypeEnum.SELECT.equals(sqlCommandTypeEnum)) {
        return new MethodNameToSqlSelectMethod(method, tableInfo, mapperClass, this.properties);
      }
      if (SqlCommandTypeEnum.EXISTS.equals(sqlCommandTypeEnum)) {
        return new MethodNameToSqlExistsMethod(method, tableInfo, mapperClass, this.properties);
      }
      if (SqlCommandTypeEnum.COUNT.equals(sqlCommandTypeEnum)) {
        return new MethodNameToSqlCountMethod(method, tableInfo, mapperClass, this.properties);
      }
      if (SqlCommandTypeEnum.DELETE.equals(sqlCommandTypeEnum)) {
        return new MethodNameToSqlDeleteMethod(method, tableInfo, mapperClass, this.properties);
      }
    } catch (Exception ex) {

    }
    return null;
  }
}
